﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace EyesData
{
    public partial class Form1 : Form
    {
        List<TextBox> filelist = new List<TextBox>();

        Bitmap map;
        Graphics g;
        byte[] mapdata;
        byte[] sourdata;
        struct cr
        {
            public byte b;
            public byte g;
            public byte r;
            public byte a;
        }

        [StructLayout(LayoutKind.Explicit)]
        struct union
        {
            [FieldOffset(0)]
            public byte[] data;
            [FieldOffset(0)]
            public int[] color;
            [FieldOffset(0)]
            public long[] l;
            [FieldOffset(0)]
            public cr[] c;
        }

        union MAPCOLOR = new union();
        union DATACOLOR = new union();
        void setImageData(Bitmap img, byte[] dst, int w = -1, int h = -1 )
        {
            if (w < 0) w = img.Width;
            if (h < 0) h = img.Height;
            var rect = new Rectangle(0, 0, w, h);
            var bd = img.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, img.PixelFormat);
            IntPtr ptr = bd.Scan0;
            int len = bd.Stride * bd.Height;
            byte[] data = new byte[len];
            Marshal.Copy(ptr, data, 0, len);
            for (int i = 0; i < dst.Length; i++)
                data[i] = dst[i];
            Marshal.Copy(data, 0, ptr, len);
            img.UnlockBits(bd);
        }

        byte[] getImageData(Bitmap img, int w = -1, int h = -1)
        {
            if (w < 0) w = img.Width; 
            if (h < 0) h = img.Height;
            var rect = new Rectangle(0, 0, w, h);
            var bd = img.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, img.PixelFormat);
            IntPtr ptr = bd.Scan0;
            int len = bd.Stride * bd.Height;
            byte[] data = new byte[len];
            Marshal.Copy(ptr, data, 0, len);
            img.UnlockBits(bd);
            return data;
        }
        public Form1()
        {
            InitializeComponent();

            map = new Bitmap(128,128);
            g=Graphics.FromImage(map);
            mapdata = new byte[map.Width * map.Height * 4];
            MAPCOLOR.data = mapdata;
            filelist.Add(textBox1);
            filelist.Add(textBox2);
            filelist.Add(textBox3);
            filelist.Add(textBox4);
            filelist.Add(textBox5);

            comboBox1.Items.Add("defaultEye");
            comboBox1.Items.Add("catEye");
            comboBox1.Items.Add("doeEye");
            comboBox1.Items.Add("dragonEye");
            comboBox1.Items.Add("goatEye");
            comboBox1.Items.Add("naugaEye");
            comboBox1.Items.Add("newtEye");
            comboBox1.Items.Add("noScleraEye");
            comboBox1.Items.Add("terminatorEye");

            Timer t = new Timer();
            t.Interval = 33;
            t.Tick += (s, e) => {
                if (sourdata != null)
                    update();
            };
            t.Start();
        }

        

        OpenFileDialog of = new OpenFileDialog();
        private void loadfile(object sender, EventArgs e)
        {
            var b = (sender as Button);
            if (b != null)
            {
                of.Filter = "*.jpg,*.png|*.jpg;*.png";
                if(of.ShowDialog() != DialogResult.OK)return;
                var index = int.Parse(b.Tag as string);
                filelist[index].Text = of.FileName;
                builddata();
                maxlast = 255;
                framlate = 120;
            }
        }
        int pgm_read_word(int pgm)
        {
            return DATACOLOR.color[pgm];
        }

        byte pgm_read_byte(int pgm) { return DATACOLOR.c[pgm].r; }

        int writeindex = 0;
        void startwrite()
        {
            writeindex = 0;
        }
        void endwrite()
        {
            setImageData(map, mapdata);
        }
        void writecolor(int color)
        {
            MAPCOLOR.color[writeindex++] = color;
        }
        Bitmap loadimage(string path)
        {
            try
            {

            var img=Image.FromFile(path);
            var b=new Bitmap(img.Width, img.Height);
            using(var g = Graphics.FromImage(b))
            {
                g.Clear(Color.Black);
                g.DrawImage(img,0,0,img.Width,img.Height);
            }
            return b;
            }
            catch { return null; }
        }
        void buildpolar(string path,MemoryStream ms)
        {
            var IRIS_SIZE = 80;
            Bitmap b = null;
            byte[] data=null;
            union bd = new union();
            if (path != "")
            {
                if (File.Exists(path))
                {

                b = loadimage(path);
                if (b.Width == b.Height)
                {
                    data = getImageData(b);
                    bd.data = data;
                    IRIS_SIZE = b.Width;
                }
                }
                else
                {
                    try
                    {
                        IRIS_SIZE = int.Parse(path);
                    }
                    catch { }
                }

            }
            IRIS_WIDTH = IRIS_HEIGHT = IRIS_SIZE;
            var RADIUS = IRIS_WIDTH / 2;
            for (int y = 0; y < IRIS_SIZE; y++)
            {
                var dy = y - RADIUS + 0.5;
                for (int x = 0; x < IRIS_SIZE; x++)
                {
                    var dx = x - RADIUS + 0.5;
                    var dis=Math.Sqrt(dx*dx+dy*dy);
                    if (dis >= RADIUS)
                    {
                        ms.WriteByte(127);
                        ms.WriteByte(0);
                        ms.WriteByte(0);
                        ms.WriteByte(0);
                    }
                    else
                    {
                        var angle = Math.Atan2(dy, dx);
                        angle += Math.PI;
                        angle /= (Math.PI * 2);
                        if (data != null)
                            dis = bd.c[IRIS_SIZE * y + x].r / 255.0;
                        else
                            dis /= RADIUS;
                        dis *= 128;
                        if (dis > 127)
                            dis = 127;
                        var a = (int)(angle * 512);
                        var d = 127 - (int)dis;
                        a = (a << 7) | d;
                        ms.WriteByte((byte)(a & 0xff));
                        ms.WriteByte((byte)((a >> 8) & 0xff));
                        ms.WriteByte(0);
                        ms.WriteByte(0);
                    }
                }
            }
        }
        bool builddata()
        {
            return builddata(new string[] { filelist[0].Text, filelist[1].Text, filelist[2].Text, filelist[3].Text, filelist[4].Text });
        }
        bool builddata(string[] paths)
        {
            int index = 0;
            if (paths.Length < 5) return false;
            foreach (var p in paths)
            {
                if (index++ == 2) continue;
                if (p == "") return false;
                if (!File.Exists(p)) return false;
            }
            index = 0;
            using (MemoryStream ms = new MemoryStream())
            {
                foreach (var p in paths)
                {
                    switch (index)
                    {
                        case 0:
                            sclera = (int)ms.Position/4; break;
                        case 1:
                            iris = (int)ms.Position / 4; break;
                        case 2:
                            polar = (int)ms.Position / 4;
                            buildpolar(p, ms);
                            index++;
                            continue;
                        case 3:
                            upper = (int)ms.Position / 4; break;
                        case 4:
                            lower = (int)ms.Position / 4; break;
                    }
                    using (Bitmap b = loadimage(p))
                    {

                        var dat = getImageData(b);
                        ms.Write(dat, 0, dat.Length);
                        switch (index)
                        {
                            case 0:
                                SCLERA_HEIGHT = b.Height;
                                SCLERA_WIDTH = b.Width;
                                break;
                            case 1:
                                IRIS_MAP_HEIGHT = b.Height;
                                IRIS_MAP_WIDTH = b.Width;
                                break;
                            case 2:
                                break;
                            case 3:

                                break;
                            case 4:

                                break;
                        }
                    }
                    index++;
                }
                sourdata = ms.ToArray();
            }
            DATACOLOR.data = sourdata;
            return true;
        }

        int SCREEN_HEIGHT = 128;
        int SCREEN_WIDTH = 128;
        
        int SCLERA_HEIGHT = 200;
        int SCLERA_WIDTH = 200;

        int IRIS_HEIGHT = 80;
        int IRIS_WIDTH = 80;

        int IRIS_MAP_WIDTH = 256;
        int IRIS_MAP_HEIGHT = 64;

        int BUFFER_SIZE = 128;

        int lower =0;
        int upper = 0;
        int sclera = 0;
        int polar = 0;
        int iris = 0;

        // EYE-RENDERING FUNCTION --------------------------------------------------
        void drawEye( // Renders one eye.  Inputs must be pre-clipped & valid.
                      // Use native 32 bit variables where possible as this is 10% faster!
          int e,       // Eye array index; 0 or 1 for left/right
          int iScale,  // Scale factor for iris
          int scleraX, // First pixel X offset into sclera image
          int scleraY, // First pixel Y offset into sclera image
          int uT,      // Upper eyelid threshold value
          int lT)
        {    // Lower eyelid threshold value

            int screenX, screenY, scleraXsave;
            int irisX, irisY;
            int p, a;
            int d;

            int pixels = 0;

            // Set up raw pixel dump to entire screen.  Although such writes can wrap
            // around automatically from end of rect back to beginning, the region is
            // reset on each frame here in case of an SPI glitch.

            startwrite();
            //tft.startWrite();
            //tft.setAddrWindow(eye[e].xposition + 32, 0, 256, 256);

            // Now just issue raw 16-bit values for every pixel...

            scleraXsave = scleraX; // Save initial X value to reset on each line
            irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;

            // Eyelid image is left<>right swapped for two displays
            int lidX = 0;
            int dlidX = -1;
            if (e!=0) dlidX = 1;
            for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++)
            {
                scleraX = scleraXsave;
                irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
                if (e != 0) lidX = 0; else lidX = SCREEN_WIDTH - 1;
                for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++, lidX += dlidX)
                {
                    if ((pgm_read_byte(lower + screenY * SCREEN_WIDTH + lidX) <= lT) ||
                        (pgm_read_byte(upper + screenY * SCREEN_WIDTH + lidX) <= uT))
                    {              // Covered by eyelid
                        //union ax= new union();
                        //ax.data = new byte[4];
                        //ax.c[0].a = 0xff;
                        //ax.c[0].r = 0xff;
                        //p = ax.color[0];
                        p = 0xff << 24;
                    }
                    else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
                               (irisX < 0) || (irisX >= IRIS_WIDTH))
                    { // In sclera
                        p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
                    }
                    else
                    {                                          // Maybe iris...
                        p = pgm_read_word(polar + irisY * IRIS_WIDTH + irisX);                        // Polar angle/dist
                        d = (iScale * (p & 0x7F)) / 128;                // Distance (Y)
                        if (d < IRIS_MAP_HEIGHT)
                        {                      // Within iris area
                            a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;        // Angle (X)
                            p = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a);                           // Pixel = iris
                        }
                        else
                        {                                        // Not in iris
                            p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);               // Pixel = sclera
                        }
                    }
                    //uint32_t val = p >> 8 | p << 8;
                    //pbufferx2[pixels++] = (val << 16) | val;
                    //*(&pbuffer[dmaBuf][0] + pixels++) = p >> 8 | p << 8;
                    writecolor(p);
                    if (pixels >= BUFFER_SIZE)
                    {
                        //tft.pushPixels(pbufferx2, pixels * 2);
                        //tft.pushPixels(pbufferx2, pixels * 2);
                        pixels = 0;
                    }
                }
            }

            //if (pixels!=0)
            //{
            //    tft.pushPixels(pbuffer, pixels);
            //}
            //tft.endWrite();
            endwrite();
        }

        int IRIS_MIN = 75;
        int IRIS_MAX = 100;
        Random random = new Random();


        // Process motion for a single frame of left or right eye
        static bool eyeInMotion = false;
        static int eyeOldX = 512, eyeOldY = 512, eyeNewX = 512, eyeNewY = 512;
        static long eyeMoveStartTime = 0;
        static int eyeMoveDuration = 0;
        static int frames = 0; // Used in frame rate calculation
        static int eyeIndex = 0; // eye[] array counter
        long timeOfLastBlink = 0, timeToNextBlink = 0;

        int[] ease =new int[]{ // Ease in/out curve for eye movements 3*t^2-2*t^3
          0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  2,  2,  2,  3,   // T
          3,  3,  4,  4,  4,  5,  5,  6,  6,  7,  7,  8,  9,  9, 10, 10,   // h
          11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23,   // x
          24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39,   // 2
          40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,   // A
          60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80,   // l
          81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98, 100, 101, 103, // e
          104, 106, 107, 109, 110, 112, 113, 115, 116, 118, 119, 121, 122, 124, 125, 127, // c
          128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, // J
          152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 167, 168, 170, 171, 172, 174, // a
          175, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 192, 193, 194, 195, // c
          197, 198, 199, 201, 202, 203, 204, 205, 207, 208, 209, 210, 211, 213, 214, 215, // o
          216, 217, 218, 219, 220, 221, 222, 224, 225, 226, 227, 228, 228, 229, 230, 231, // b
          232, 233, 234, 235, 236, 237, 237, 238, 239, 240, 240, 241, 242, 243, 243, 244, // s
          245, 245, 246, 246, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, // o
          252, 253, 253, 253, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255
        }; // n
        const int NOBLINK = 0;
        const int ENBLINK = 1;
        const int DEBLINK = 2;
        int NUM_EYES = 1;
        int eye_blink_state= NOBLINK;
        int eye_blink_wink;
        long eye_blink_startTime;
        int eye_blink_duration;
        static int uThreshold = 128;
        int maxlast = 255;
        int framlate = 1024;
        bool WINK=false;
        void frame(int iScale) // Iris scale (0-1023)
        {

            int eyeX, eyeY;
            long t = (DateTime.Now.Ticks/10); // Time at start of function
            //t &= 0x7fffffff;
            
            eyeIndex = 0; // Cycle through eyes, 1 per call

            // X/Y movement


            // Periodically initiates motion to a new random point, random speed,
            // holds there for random period until next motion.


            long dt = t - eyeMoveStartTime;      // uS elapsed since last eye event
            if (eyeInMotion)
            {                      // Currently moving?
                if (dt >= eyeMoveDuration)
                {          // Time up?  Destination reached.
                    eyeInMotion = false;           // Stop moving
                    eyeMoveDuration = random.Next(3000000); // 0-3 sec stop
                    eyeMoveStartTime = t;               // Save initial time of stop
                    eyeX = eyeOldX = eyeNewX;           // Save position
                    eyeY = eyeOldY = eyeNewY;
                }
                else
                { // Move time's not yet fully elapsed -- interpolate position
                    int e = ease[255 * dt / eyeMoveDuration] + 1;   // Ease curve
                    eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
                    eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
                }
            }
            else
            {                                // Eye stopped
                if (checkBox1.Checked)
                {
                    var dp = Cursor.Position;
                    eyeX = (dp.X- 64 ) - (Location.X + 64 + pictureBox1.Location.X) ;
                    eyeY = (dp.Y- 64 ) - (Location.Y + 64 + pictureBox1.Location.Y);
                    if (eyeX < -64) eyeX = -64;
                    eyeX += 64;
                    if (eyeX > 64) eyeX = 64;
                    eyeX *= 16;
                    if(eyeY < -64) eyeY = -64;
                    eyeY += 64;
                    if (eyeY > 64)eyeY = 64;
                    eyeY *= 16;
                    var dx = (eyeX * 2) - 1023; // A/D exact center is at 511.5.  Scale coords
                    var dy = (eyeY * 2) - 1023; // X2 so range is -1023 to +1023 w/center at 0.
                    var d = 0;
                    if ((d = (dx * dx + dy * dy)) > (1023 * 1023))
                    { // Outside circle
                        d = (int)Math.Sqrt(d);               // Distance from center
                        eyeX = ((-dx * 1023 / d) + 1023) / 2;          // Clip to circle edge,
                        eyeY = ((-dy * 1023 / d) + 1023) / 2;          // scale back to 0-1023
                    }
                }
                else if (checkBox2.Checked)
                {
                    eyeX = trackEyex.Value * 1024 / trackEyex.Maximum;
                    eyeY = trackEyey.Value * 1024 / trackEyey.Maximum;
                }
                else
                {


                    eyeX = eyeOldX;
                    eyeY = eyeOldY;
                    if (dt > eyeMoveDuration)
                    {           // Time up?  Begin new move.
                        int dx, dy;
                        int d;
                        do
                        {                                // Pick new dest in circle
                            eyeNewX = random.Next(1024);
                            eyeNewY = random.Next(1024);
                            dx = (eyeNewX * 2) - 1023;
                            dy = (eyeNewY * 2) - 1023;
                        } while ((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
                        eyeMoveDuration = random.Next(72000, 144000); // ~1/14 - ~1/7 sec
                        eyeMoveStartTime = t;               // Save initial time of move
                        eyeInMotion = true;            // Start move on next frame
                    }
                }
            }


            // Blinking

            // Similar to the autonomous eye movement above -- blink start times
            // and durations are random (within ranges).
            if ((t - timeOfLastBlink) >= timeToNextBlink)
            { // Start new blink?
                timeOfLastBlink = t;
                int blinkDuration = random.Next(36000, 72000); // ~1/28 - ~1/14 sec
                                                               // Set up durations for both eyes (if not already winking)
                for (int e = 0; e < NUM_EYES; e++)
                {
                    if (eye_blink_state == NOBLINK)
                    {
                        eye_blink_state = ENBLINK;
                        eye_blink_startTime = t;
                        eye_blink_duration = blinkDuration;
                    }
                }
                timeToNextBlink = blinkDuration * 3 + random.Next(4000000);
            }


            if (eye_blink_state!=NOBLINK)
            { // Eye currently blinking?
              // Check if current blink state time has elapsed
                if ((t - eye_blink_startTime) >= eye_blink_duration)
                {
                    // Yes -- increment blink state, unless...
                    if ((eye_blink_state!=NOBLINK) && ( // Enblinking and...
                          WINK))
                    {
                        // Don't advance state yet -- eye is held closed instead
                    }
                    else
                    { // No buttons, or other state...
                        if (++eye_blink_state > DEBLINK)
                        { // Deblinking finished?
                            eye_blink_state = NOBLINK;      // No longer blinking
                        }
                        else
                        { // Advancing from ENBLINK to DEBLINK mode
                            eye_blink_duration *= 2; // DEBLINK is 1/2 ENBLINK speed
                            eye_blink_startTime = t;
                        }
                    }
                }
            }
            else
            { // Not currently blinking...check buttons!

                if (WINK)
                { // Wink!
                    eye_blink_state = ENBLINK;
                    eye_blink_startTime = t;
                    eye_blink_duration = random.Next(45000, 90000);
                }
            }

            // Process motion, blinking and iris scale into renderable values

            // Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
            eyeX = eyeX * (SCLERA_WIDTH - SCREEN_WIDTH) / 1024;
            eyeY = eyeY * (SCLERA_HEIGHT - SCREEN_HEIGHT) / 1024;

            // Horizontal position is offset so that eyes are very slightly crossed
            // to appear fixated (converged) at a conversational distance.  Number
            // here was extracted from my posterior and not mathematically based.
            // I suppose one could get all clever with a range sensor, but for now...
            if (NUM_EYES > 1)
            {
                if (eyeIndex == 1) eyeX += 4;
                else eyeX -= 4;
            }
            if (eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);

            // Eyelids are rendered using a brightness threshold image.  This same
            // map can be used to simplify another problem: making the upper eyelid
            // track the pupil (eyes tend to open only as much as needed -- e.g. look
            // down and the upper eyelid drops).  Just sample a point in the upper
            // lid map slightly above the pupil to determine the rendering threshold.
            int lThreshold, n;

            if (checkBox2.Checked&& checkBox4.Checked==false)
            {
                n = trackupper.Value;
                lThreshold = tracklower.Value;
            }
            else
            {
                int sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence
                        sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
                // Eyelid is slightly asymmetrical, so two readings are taken, averaged
                if (sampleY < 0) n = 0;
                else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) +
                                       pgm_read_byte(upper + sampleY * SCREEN_WIDTH + (SCREEN_WIDTH - 1 - sampleX))) / 2;
                uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
                                                       // Lower eyelid doesn't track the same way, but seems to be pulled upward
                                                       // by tension from the upper lid.
                lThreshold = 254 - uThreshold;


                // The upper/lower thresholds are then scaled relative to the current
                // blink position so that blinks work together with pupil tracking.
                if (eye_blink_state != NOBLINK)
                { // Eye currently blinking?
                    long s = (t - eye_blink_startTime);
                    if (s >= eye_blink_duration) s = 255;  // At or past blink end
                    else s = 255 * s / eye_blink_duration; // Mid-blink
                    s = (eye_blink_state == DEBLINK) ? 1 + s : 256 - s;
                    n = (int)(uThreshold * s + 254 * (257 - s)) / 256;
                    lThreshold = (int)(lThreshold * s + 254 * (257 - s)) / 256;
                }
                else
                {
                    n = uThreshold;
                }
            }
            labelinfo.Text = $"X:{eyeX} Y:{eyeY} Iris:{iScale} upper:{n} lower:{lThreshold}";
            // Pass all the derived values to the eye-rendering function:
            if (framlate>0)
                framlate--;
            if (maxlast > n)
                maxlast = n;
            if (checkBox4.Checked)
                drawEye(eyeIndex, iScale, eyeX, eyeY, (n- (framlate==0?maxlast:0)) *2, (n - (framlate == 0 ? maxlast : 0)) * 2);
            else
                drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
        }
        SaveFileDialog sf = new SaveFileDialog();
        private void button6_Click(object sender, EventArgs e)
        {
            sf.Filter = "*.h|*.h";
            if(sf.ShowDialog() == DialogResult.OK)
            {
                using (StreamWriter sw = new StreamWriter(sf.FileName))
                {
                    sw.WriteLine($"#define BLINK_TH {maxlast-1}");

                    sw.WriteLine($"#define SCLERA_WIDTH  {SCLERA_WIDTH}");
                    sw.WriteLine($"#define SCLERA_HEIGHT {SCLERA_HEIGHT}");
                    sw.WriteLine();
                    sw.WriteLine("const uint16_t sclera[SCLERA_HEIGHT * SCLERA_WIDTH] PROGMEM= {");
                    int ln = 0;
                    for (int y = 0; y < SCLERA_HEIGHT; y++)
                    {
                        for (int x = 0; x < SCLERA_WIDTH; x++)
                        {
                            var c = DATACOLOR.c[sclera + y * SCLERA_WIDTH + x];
                            //var val = (((int)c.b & 0xf8) << 8) | (((int)c.g & 0xfc) << 3) | ((int)c.r >> 3);
                            var val = (((int)c.r & 0xf8) << 8) | (((int)c.g & 0xfc) << 3) | ((int)c.b >> 3);
                            sw.Write($"0x{val:X4},");
                            if (++ln == 8)
                            {
                                sw.WriteLine();
                                ln = 0;
                            }
                        }
                    }
                    sw.WriteLine("};");

                    sw.WriteLine();
                    sw.WriteLine($"#define IRIS_MAP_WIDTH  {IRIS_MAP_WIDTH}");
                    sw.WriteLine($"#define IRIS_MAP_HEIGHT {IRIS_MAP_HEIGHT}");
                    sw.WriteLine();
                    sw.WriteLine("const uint16_t iris[IRIS_MAP_HEIGHT * IRIS_MAP_WIDTH] PROGMEM= {");
                    ln = 0;
                    for (int y = 0; y < IRIS_MAP_HEIGHT; y++)
                    {
                        for (int x = 0; x < IRIS_MAP_WIDTH; x++)
                        {
                            var c = DATACOLOR.c[iris + y * IRIS_MAP_WIDTH + x];
                            //var val = (((int)c.b & 0xf8) << 8) | (((int)c.g & 0xfc) << 3) | ((int)c.r >> 3);
                            var val = (((int)c.r & 0xf8) << 8) | (((int)c.g & 0xfc) << 3) | ((int)c.b >> 3);
                            sw.Write($"0x{val:X4},");
                            if (++ln == 8)
                            {
                                sw.WriteLine();
                                ln = 0;
                            }
                        }
                    }
                    sw.WriteLine("};");


                    sw.WriteLine();
                    sw.WriteLine($"#define IRIS_WIDTH  {IRIS_WIDTH}");
                    sw.WriteLine($"#define IRIS_HEIGHT {IRIS_HEIGHT}");
                    sw.WriteLine();
                    sw.WriteLine("const uint16_t polar[IRIS_WIDTH * IRIS_HEIGHT] PROGMEM= {");
                    ln = 0;
                    for (int y = 0; y < IRIS_HEIGHT; y++)
                    {
                        for (int x = 0; x < IRIS_WIDTH; x++)
                        {
                            var c = DATACOLOR.color[polar + y * IRIS_WIDTH + x];
                            sw.Write($"0x{c:X4},");
                            if (++ln == 8)
                            {
                                sw.WriteLine();
                                ln = 0;
                            }
                        }
                    }
                    sw.WriteLine("};");


                    sw.WriteLine();
                    sw.WriteLine($"#define SCREEN_WIDTH  {128}");
                    sw.WriteLine($"#define SCREEN_HEIGHT {128}");
                    sw.WriteLine();
                    sw.WriteLine("const uint8_t upper[SCREEN_HEIGHT * SCREEN_WIDTH] PROGMEM= {");
                    ln = 0;
                    for (int y = 0; y < SCREEN_HEIGHT; y++)
                    {
                        for (int x = 0; x < SCREEN_WIDTH; x++)
                        {
                            var c = DATACOLOR.c[upper + y * SCREEN_WIDTH + x];
                            var val = c.r * 299 / 1000 + c.g * 587 / 1000 + c.b * 114 / 1000; //var val = (((int)c.b & 0xf8) << 8) | (((int)c.g & 0xfc) << 3) | ((int)c.r >> 3);
                            sw.Write($"0x{val:X2},");
                            if (++ln == 8)
                            {
                                sw.WriteLine();
                                ln = 0;
                            }
                        }
                    }
                    sw.WriteLine("};");

                    sw.WriteLine();
                    sw.WriteLine("const uint8_t lower[SCREEN_HEIGHT * SCREEN_WIDTH] PROGMEM= {");
                    ln = 0;
                    for (int y = 0; y < SCREEN_HEIGHT; y++)
                    {
                        for (int x = 0; x < SCREEN_WIDTH; x++)
                        {
                            var c = DATACOLOR.c[lower + y * SCREEN_WIDTH + x];
                            var val = c.r * 299 / 1000 + c.g * 587 / 1000 + c.b * 114 / 1000;// (((int)c.b & 0xf8) << 8) | (((int)c.g & 0xfc) << 3) | ((int)c.r >> 3);
                            sw.Write($"0x{val:X2},");
                            if (++ln == 8)
                            {
                                sw.WriteLine();
                                ln = 0;
                            }
                        }
                    }
                    sw.WriteLine("};");

                }
            }
        }

        private void trackupper_Scroll(object sender, EventArgs e)
        {
            if(checkBox3.Checked)
            tracklower.Value = trackupper.Value;
        }

        void split( // Subdivides motion path into two sub-paths w/randimization
  int startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
  int endValue,   // Iris scale value at end
  int startTime,  // micros() at start
  int duration,   // Start-to-end time, in microseconds
  int range)
        {    // Allowable scale value variance when subdividing

            if (range >= 8)
            {    // Limit subdvision count, because recursion
                range /= 2;     // Split range & time in half for subdivision,
                duration /= 2;     // then pick random center point within range:
                int midValue = (startValue + endValue - range) / 2 +  random.Next(range);
                int midTime = startTime + duration;
                split(startValue, midValue, startTime, duration, range); // First half
                split(midValue, endValue, midTime, duration, range); // Second half
            }
            else
            {             // No more subdivisons, do iris motion...
                int dt;        // Time (micros) since start of motion
                int v;         // Interim value
                while ((dt = ((int)(DateTime.Now.Ticks/10000) - startTime)) < duration)
                {
                    v = startValue + (((endValue - startValue) * dt) / duration);
                    if (v < IRIS_MIN) v = IRIS_MIN; // Clip just in case
                    else if (v > IRIS_MAX) v = IRIS_MAX;
                    frame(v);        // Draw frame w/interim iris scale value
                }
            }
        }

        int oldIris = (75 + 130) / 2, newIris= (75 + 130) / 2;
        int stopcount = 0;
        void update()
        {
            
            if (newIris != oldIris)
            {
                var off = (int)((newIris - oldIris) * 0.015);
                if (newIris > oldIris)
                    off = off == 0 ? 1 : off;
                else
                    off = off == 0 ? -1 : off;
                oldIris+=off;
            }
            else
            {
                if (--stopcount <= 0)
                {
                    stopcount = random.Next(35, 90);
                    newIris = random.Next(IRIS_MIN, IRIS_MAX);
                }
            }
            if(checkBox2.Checked)
            {
                frame(trackiris.Value);
            }
            else
            frame(oldIris);
            

            //drawEye(0, 75, 50, 50, 0, 0);
            setImageData(map, MAPCOLOR.data);
            pictureBox1.Image = map;
        }
    }
}
